在 Day 08 中學到的 Deployment 是專門為「無狀態應用(如 Web 前端、無狀態 REST API)」設計的:
nginx-75675f5897-abcde),死掉重建後會換一個全新的隨機名字。但分散式資料庫(如 MySQL 主從複製、Redis 集群、Kafka)有嚴格的「身分認同」需求:
為了解決這些問題,Kubernetes 推出了專門管理有狀態服務的控制器——StatefulSet。
| 特性 | Deployment (無狀態) | StatefulSet (有狀態) |
|---|---|---|
| Pod 命名 | 隨機後綴(如 web-a1b2c) |
確定性有序編號(如 db-0, db-1, db-2) |
| 啟動/銷毀順序 | 同時並行(Parallel) | 嚴格依序建立(0➔1➔2),倒序銷毀(2➔1➔0) |
| 儲存綁定 | 多副本共享同一份 PVC | volumeClaimTemplates 為每個 Pod 自動建立專屬獨立 PVC |
| 網路識別 | 透過一般 Service 負載平衡隨機轉發 | 搭配 Headless Service 提供穩定的單獨 Pod DNS |
一般 Service 會配置一個虛擬 IP(ClusterIP),將流量隨機負載平衡轉發給後端 Pod。
但有狀態應用需要直接連線到「特定的某一個節點」(例如應用程式必須明確連線到 db-0 進行寫入)。
Headless Service 就是將 clusterIP: None 的 Service:
$(pod-name).$(service-name).$(namespace).svc.cluster.localdb-0.db-service.default.svc.cluster.local)建立 statefulset-demo.yaml,包含一個無頭服務與一個 3 副本的 StatefulSet:
apiVersion: v1
kind: Service
metadata:
name: db-headless-service
spec:
clusterIP: None
selector:
app: stateful-db
ports:
- port: 80
name: web
---
apiVersion: apps/v1
kind: StatefulSet
metadata:
name: stateful-db
spec:
serviceName: "db-headless-service" # 綁定 Headless Service
replicas: 3
selector:
matchLabels:
app: stateful-db
template:
metadata:
labels:
app: stateful-db
spec:
containers:
- name: app
image: nginx:1.25
ports:
- containerPort: 80
name: web
volumeMounts:
- name: db-data
mountPath: /usr/share/nginx/html
# 為每個副本自動動態產生獨立的 PVC
volumeClaimTemplates:
- metadata:
name: db-data
spec:
accessModes: ["ReadWriteOnce"]
resources:
requests:
storage: 1Gi
套用配置:
kubectl apply -f statefulset-demo.yaml
打開另一個終端機觀察 Pod 建立的順序:
kubectl get pods -l app=stateful-db -w
你會看到嚴格的循序漸進過程:
stateful-db-0 先進入 ContainerCreating ➔ Running。stateful-db-1 才開始建立。stateful-db-2。查看系統中的 PVC:
kubectl get pvc
預期輸出:
NAME STATUS VOLUME CAPACITY ACCESS MODES AGE
db-data-stateful-db-0 Bound pvc-11111111-2222-3333-4444-555555555555 1Gi RWO 1m
db-data-stateful-db-1 Bound pvc-22222222-3333-4444-5555-666666666666 1Gi RWO 1m
db-data-stateful-db-2 Bound pvc-33333333-4444-5555-6666-777777777777 1Gi RWO 1m
Kubernetes 根據 volumeClaimTemplates,自動為 0、1、2 各自建立了一顆完全隔離的專屬磁碟!
我們臨時起一個測試容器,驗證是否可以直接透過域名精準指定存取 stateful-db-0:
kubectl run dns-test --image=curlimages/curl --rm -it -- restart=Never -- nslookup stateful-db-0.db-headless-service
CoreDNS 會直接解析出 stateful-db-0 的專屬 IP 位址!即使 stateful-db-0 被刪除重建,新生的 Pod 依然會繼承這個域名與原本的 PVC 磁碟。
今天我們搞懂了專門處理有狀態應用的 StatefulSet:
volumeClaimTemplates 讓每個 Pod 都享有自己專屬、不共用的持久化磁碟。到目前為止,我們學到的 Deployment 與 StatefulSet 都是「長時間持續運行(Long-running)」的服務。但有些工作只需要跑一次就結束(例如資料庫遷移、批次報表計算、定時備份腳本)。
明天 Day 18,我們將學習排程與一次性任務的利器:「排程與一次性任務:Job 與 CronJob 自動化執行腳本」!